﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using CommonLib;
using Constructor;
using System.Runtime.InteropServices;
using Constructor.Frames;

namespace MLO
{
    public static class MLOSource
    {
        public static DataSet all_data = new DataSet("all_data");

        private static List<Frame> _AllFrames = new List<Frame>();
        public static List<Frame> AllFrames { get { return _AllFrames; } }
        private static List<Frame> _Situations = new List<Frame>();
        public static List<Frame> Situations { get { return _Situations; } }
        private static List<Frame> _Exemplars = new List<Frame>();
        public static List<Frame> Exemplars { get { return _Exemplars; } }
        private static List<Frame> _Prototypes = new List<Frame>();
        public static List<Frame> Prototypes { get { return _Prototypes; } }
        private static List<Frame> _DeletionExemplars = new List<Frame>();
        public static List<Frame> DeletionExemplars { get { return _DeletionExemplars; } }
        private static List<Frame> _ActiveSituations = new List<Frame>();
        public static List<Frame> ActiveSituations { get { return _ActiveSituations; } }

        private static Dictionary<string, Frame> _NotExemplarFrames = new Dictionary<string, Frame>();
        public static Dictionary<string, Frame> NotExemplarFrames { get { return _NotExemplarFrames; } }

        private static Dictionary<string, Frame> _ExemplarFrames = new Dictionary<string, Frame>();
        public static Dictionary<string, Frame> ExemplarFrames { get { return _ExemplarFrames; } }

        public static void Initialize()
        {
            AllFrames.Clear();
            Situations.Clear();
            ActiveSituations.Clear();
            NotExemplarFrames.Clear();
            ExemplarFrames.Clear();
            DeletionExemplars.Clear();

            foreach (var oldFrame in External.frames)
            {
                Frame NewFrame = oldFrame.Clone() as Frame;
                MLOSource.AllFrames.Add(NewFrame);
                NotExemplarFrames.Add(NewFrame.ID, NewFrame);
                if (NewFrame.Type == FrameType.Prototype)
                    MLOSource.Prototypes.Add(NewFrame);
                if (NewFrame.Type == FrameType.Situation)
                {
                    MLOSource.Situations.Add(NewFrame);
                    if (NewFrame.Priority < 0) continue;
                    var productionSlots = NewFrame.GetSlots(SlotType.Production, true);
                    if (productionSlots.Count > 0)
                    {
                        bool NotOnlyMarkerProductions = false;
                        for (int i = 0; i < productionSlots.Count && !NotOnlyMarkerProductions; i++)
                            NotOnlyMarkerProductions = !productionSlots[i].Production.Marker;
                        if (NotOnlyMarkerProductions) ActiveSituations.Add(NewFrame);
                    }
                }
            }
            ActiveSituations.Sort(FramePriorityCompare);
            MLOSource.AllFrames.AddRange(MLOSource.Exemplars);
            foreach (var exemplar in MLOSource.Exemplars)
                ExemplarFrames.Add(exemplar.ID, exemplar);
        }

        private static int FramePriorityCompare(Frame f1, Frame f2)
        {
            if (f1.Priority < f2.Priority)
                return 1;
            if (f1.Priority > f2.Priority)
                return -1;
            return 0;
        }

        public static List<Frame> GetActiveSituations()
        {
            var result = new List<Frame>();

            return result;
        }


        public class MLOException : Exception
        {
            public MLOException(string message)
                : base(message)
            {

            }
            public MLOException(string message, Exception innerException)
                : base(message, innerException)
            {

            }
        }

        /// <summary>
        /// Возвращает слот из строки разделенной точками.
        /// </summary>
        public static Slot GetSlotBySplit(string split, Frame start)
        {
            if (!split.Contains('.'))
            {
                if (start != null)
                    return start.SearchInAllSlot(split);
            }
            else
                return GetSlotBySplit(split.Substring(split.IndexOf('.') + 1),
                    start.SearchInAllSlot(split.Substring(0, split.IndexOf('.'))).Subframe);
            return null;
        }

        /// <summary>
        /// Возвращает значение процедуры.
        /// </summary>
        public static int ProcessProcedure(string name, List<NewItem> itms)
        {
            switch (name)
            {
                case "Удалить_с_поля":
                    Frame deleted = itms[0].Content.Subframe;
                    DeletionExemplars.Add(deleted);
                    return 0;
                case "Конец_игры":
                    External.ExitMLO = true;
                    return 0;
                case "КоличествоВрагов":
                    {
                        Frame chief = itms[0].Content.Subframe;
                        int result = 0;
                        foreach (Frame frm in BindSubFrames(NotExemplarFrames["Воин"]))
                        {
                            Slot chf = frm.SearchInAllSlot("Вождь");
                            if (chf != null && chf.Subframe != chief)
                                result++;
                        }
                        return result;
                    }
                case "КоличествоВоинов":
                    {
                        Frame chief = itms[0].Content.Subframe;
                        int result = 0;
                        foreach (Frame frm in BindSubFrames(NotExemplarFrames["Воин"]))
                        {
                            Slot chf = frm.SearchInAllSlot("Вождь");
                            if (chf != null && chf.Subframe == chief)
                                result++;
                        }
                        return result;
                    }
                case "Расстояние":
                    {
                        Slot cell1 = itms[0].Content.Subframe.SearchInAllSlot("Позиция");
                        Slot cell2 = itms[1].Content.Subframe.SearchInAllSlot("Позиция");
                        int result = (int)Math.Sqrt(
                            Math.Pow(int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()) - int.Parse(cell2.Subframe.SearchInAllSlot("X").Default.ToString()), 2)
                            + Math.Pow(int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()) - int.Parse(cell2.Subframe.SearchInAllSlot("Y").Default.ToString()), 2));
                        return result;
                    }
                case "КлеткаX":
                    {
                        Slot cell1 = itms[0].Content.Subframe.SearchInAllSlot("Позиция");
                        Slot cell2 = itms[0].Content.Subframe.SearchInAllSlot("Цель движения");

                        /*if (int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()) >
                            int.Parse(cell2.Subframe.SearchInAllSlot("X").Default.ToString()))
                            return int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()) - 1;
                        if (int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()) <
                            int.Parse(cell2.Subframe.SearchInAllSlot("X").Default.ToString()))
                            return int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()) + 1;

                        return int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString());*/
                        return ComputeNextCell(
                            int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()),
                            int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()),
                            int.Parse(cell2.Subframe.SearchInAllSlot("X").Default.ToString()),
                            int.Parse(cell2.Subframe.SearchInAllSlot("Y").Default.ToString())
                            )[0];
                    }
                case "КлеткаY":
                    {
                        Slot cell1 = itms[0].Content.Subframe.SearchInAllSlot("Позиция");
                        Slot cell2 = itms[0].Content.Subframe.SearchInAllSlot("Цель движения");
                        /*
                        if (int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()) >
                            int.Parse(cell2.Subframe.SearchInAllSlot("Y").Default.ToString()))
                            return int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()) - 1;
                        if (int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()) <
                            int.Parse(cell2.Subframe.SearchInAllSlot("Y").Default.ToString()))
                            return int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()) + 1;

                        return int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString());*/
                        return ComputeNextCell(
                            int.Parse(cell1.Subframe.SearchInAllSlot("X").Default.ToString()),
                            int.Parse(cell1.Subframe.SearchInAllSlot("Y").Default.ToString()),
                            int.Parse(cell2.Subframe.SearchInAllSlot("X").Default.ToString()),
                            int.Parse(cell2.Subframe.SearchInAllSlot("Y").Default.ToString())
                            )[1];

                    }
            }
            return 0;
        }

        private static bool IsCellFull(int x, int y)
        {
            foreach (var cell in MLOSource.Exemplars.FindAll(o => o.Name.Contains("Клетка")))
                if (cell.SearchSlot("X").Default.ToString() == x.ToString() && cell.SearchSlot("Y").Default.ToString() == y.ToString())
                    return cell.SearchSlot("Занятость").Default.ToString() == "Да";
            return true;
        }

        private static void GetPreviousCell(int my_x, int my_y, ref int next_x, ref int next_y)
        {
            if (next_x > my_x)
            {
                if (next_y >= my_y) next_y--;
                else next_x--;
                return;
            }
            if (next_x < my_x)
            {
                if (next_y <= my_y) next_y--;
                else next_x++;
                return;
            }
            if (next_y > my_y) next_x++;
            if (next_y < my_y) next_x--;
        }

        private static void GetNextCell(int my_x, int my_y, ref int next_x, ref int next_y)
        {
            if (next_x > my_x)
            {
                if (next_y <= my_y) next_y++;
                else next_x--;
                return;
            }
            if (next_x < my_x)
            {
                if (next_y >= my_y) next_y--;
                else next_x++;
                return;
            }
            if (next_y > my_y) next_x--;
            if (next_y < my_y) next_x++;
        }

        private static int[] ComputeNextCell(int my_x, int my_y, int goal_x, int goal_y)
        {
            int next_x = my_x, next_y = my_y;
            if (my_x < goal_x) next_x++;
            if (my_x > goal_x) next_x--;
            if (my_y < goal_y) next_y++;
            if (my_y > goal_y) next_y--;

            if (IsCellFull(next_x, next_y))
            {
                GetNextCell(my_x, my_y, ref next_x, ref next_y);
                if (IsCellFull(next_x, next_y))
                {
                    next_x = my_x; next_y = my_y;
                    if (my_x < goal_x) next_x++;
                    if (my_x > goal_x) next_x--;
                    if (my_y < goal_y) next_y++;
                    if (my_y > goal_y) next_y--;
                    GetPreviousCell(my_x, my_y, ref next_x, ref next_y);
                    if (IsCellFull(next_x, next_y))
                    {
                        next_x = my_x;
                        next_y = my_y;
                    }
                }
            }
            return new int[] { next_x, next_y };
        }

        /// <summary>
        /// Реализует операцию сравнения.
        /// </summary>
        public static bool CompareOperation(double x, string oper, double y)
        {
            switch (oper)
            {
                case ">":
                    return x > y;
                case "<":
                    return x < y;
                case ">=":
                    return x >= y;
                case "<=":
                    return x <= y;
                case "!=":
                    return x != y;
                case "=":
                    return x == y;
                default:
                    throw new MLOException("Неизвестная операция сравнения. (" + oper + ")");
            }
        }

        /// <summary>
        /// Реализует операцию над числами.
        /// </summary>
        public static double DigitOperation(double x, string oper, double y)
        {
            switch (oper)
            {
                case "+=":
                    return x += y;
                case "-=":
                    return x -= y;
                case "*=":
                    return x *= y;
                case "/=":
                    return x /= y;
                case "=":
                    return x = y;
                default:
                    throw new MLOException("Неизвестная операция над числами. (" + oper + ")");
            }
        }

        /// <summary>
        /// Возвращает список привязавшихся к фрейму-прототипу фреймов-экземпляров.
        /// </summary>
        public static List<Frame> BindSubFrames(Frame subFrame)
        {
            List<Frame> result = new List<Frame>();
            if (subFrame == null) return result;
            switch (subFrame.Type)
            {
                case FrameType.Prototype:
                    {
                        foreach (var exemplar in MLOSource.Exemplars)
                        {
                            bool binded = true;
                            int temp = 0;
                            List<Slot> prototypeSlots = subFrame.GetSlots(SlotType.Regular, true);
                            prototypeSlots.AddRange(subFrame.GetSlots(SlotType.SubFrame, true));
                            while (binded && temp < prototypeSlots.Count)
                            {
                                Slot prototypeSlot = prototypeSlots[temp++];
                                Slot exemplarSlot = exemplar.SearchInAllSlot(prototypeSlot.Name);
                                binded = binded && exemplarSlot != null && SlotEquals(prototypeSlot, exemplarSlot);
                            }
                            if (!binded) continue;

                            prototypeSlots.Clear();
                            prototypeSlots = subFrame.GetSlots(SlotType.Production, true);
                            for (int i = 0; i < prototypeSlots.Count && binded; i++)
                            {
                                Slot productionSlot = prototypeSlots[i];
                                string split = productionSlot.Production.Item.Text;
                                Slot changing = GetSlotBySplit(split.Substring(split.IndexOf('.') + 1), exemplar);
                                if (productionSlot.Production.isProc)
                                {
                                    List<NewItem> procedureParameters = new List<NewItem>();
                                    foreach (NewItem itm in productionSlot.Production.Procedure.Params)
                                    {
                                        NewItem clone = itm.Clone() as NewItem;
                                        procedureParameters.Add(clone);
                                        clone.Content.Subframe = exemplar.SearchInAllSlot(itm.Content.Name).Subframe;
                                    }
                                    int procedureResult = ProcessProcedure(productionSlot.Production.Procedure.Method.Name, procedureParameters);
                                    if (productionSlot.Production.Marker)
                                        binded = binded && CompareOperation((double)changing.Default, productionSlot.Production.Operation, (double)procedureResult);
                                    else
                                        changing.Default = changing.Domain.GetValueInRange(DigitOperation((double)changing.Default, productionSlot.Production.Operation, (double)procedureResult));
                                }
                                else
                                {
                                    switch (productionSlot.Production.Type)
                                    {
                                        case CommonLib.ValueType.Int:
                                        case CommonLib.ValueType.IntVar:
                                        case CommonLib.ValueType.Enum:
                                            {
                                                if (productionSlot.Production.isProc)
                                                {
                                                    object changerValue = null;
                                                    if (changing.Domain.Contains(split))
                                                        changerValue = split;
                                                    else
                                                    {
                                                        Slot changer = GetSlotBySplit(split.Substring(split.IndexOf('.') + 1), exemplar);
                                                        changerValue = changer.Default;
                                                    }
                                                    if (productionSlot.Production.Type == CommonLib.ValueType.Enum)
                                                    {
                                                        if (productionSlot.Production.Marker)
                                                        {
                                                            if (productionSlot.Production.Operation == "=")
                                                                binded = binded && changing.Default.ToString() == changerValue.ToString();
                                                            else
                                                                binded = binded && changing.Default.ToString() != changerValue.ToString();
                                                        }
                                                        else
                                                            changing.Default = changerValue;
                                                    }
                                                    else
                                                    {
                                                        if (productionSlot.Production.Marker)
                                                            binded = binded && CompareOperation((double)changing.Default, productionSlot.Production.Operation, (double)changerValue);
                                                        else
                                                            changing.Default = changing.Domain.GetValueInRange(DigitOperation((double)changing.Default, productionSlot.Production.Operation, (double)changerValue));
                                                    }
                                                }
                                                break;
                                            }
                                        case CommonLib.ValueType.Frame:
                                            {
                                                Slot changer = GetSlotBySplit(split.Substring(split.IndexOf('.') + 1)
                                                    , exemplar);
                                                if (productionSlot.Production.Marker)
                                                {
                                                    if (productionSlot.Production.Operation == "=")
                                                        binded = binded && changing.Subframe == changer.Subframe;
                                                    else
                                                        binded = binded && changing.Subframe != changer.Subframe;
                                                }
                                                else
                                                    changing.Subframe = changer.Subframe;
                                                break;
                                            }
                                    }
                                }
                            }

                            result.Add(exemplar);
                        }
                        break;
                    }
                case FrameType.Situation:
                    {
                        List<Slot> subFrameSlots = subFrame.GetSlots(SlotType.SubFrame, true);
                        var bindedSubFrameSets = new List<List<MLOSource.FrameSlot>>();
                        bindedSubFrameSets = MLOSource.GetBindSubframesList(subFrameSlots);
                        if (bindedSubFrameSets.Count == 0) break;

                        for (int i = bindedSubFrameSets.Count - 1; i >= 0; i--)
                        {
                            List<MLOSource.FrameSlot> lst = bindedSubFrameSets[i];
                            bool stillBinded = true;
                            if (!External.SimpleMLO)
                                for (int j = 0; j < lst.Count && stillBinded; j++)
                                {
                                    var fs = lst[j];
                                    stillBinded = stillBinded &&
                                         MLOSource.BindSubFrames(NotExemplarFrames[fs.Slot.Subframe.ID]).Contains(fs.Frame);
                                }
                            if (!stillBinded) continue;

                            Frame situationExemplar = subFrame.Clone() as Frame;
                            for (int fsn = 0; fsn < lst.Count; fsn++)
                                situationExemplar.SearchInAllSlot(lst[fsn].Slot.Name).Subframe = lst[fsn].Frame;
                            bool execute = true;
                            List<Slot> gs = situationExemplar.GetSlotsForProductions();
                            for (int j = 0; j < gs.Count && execute; j++)
                            {
                                Slot prototypeSlot = gs[j];
                                if (!(prototypeSlot.Type == SlotType.Production)) continue;

                                string split = prototypeSlot.Production.Item.Text;
                                Slot changing = MLOSource.GetSlotBySplit(split.Substring(split.IndexOf('.') + 1)
                                                , situationExemplar);
                                if (prototypeSlot.Production.isProc)
                                {
                                    List<NewItem> proc_params = new List<NewItem>();
                                    foreach (NewItem itm in prototypeSlot.Production.Procedure.Params)
                                    {
                                        proc_params.Add(itm.Clone());
                                        NewItem new_item = proc_params[proc_params.Count - 1];
                                        new_item.Content.Subframe = situationExemplar.SearchInAllSlot(itm.Content.Name).Subframe;
                                    }
                                    int procRes = MLOSource.ProcessProcedure(prototypeSlot.Production.Procedure.Method.Name, proc_params);
                                    if (prototypeSlot.Production.Marker)
                                        execute = execute && MLOSource.CompareOperation(double.Parse(changing.Default.ToString()), prototypeSlot.Production.Operation, (double)procRes);
                                    else
                                        changing.Default = changing.Domain.GetValueInRange(MLOSource.DigitOperation(double.Parse(changing.Default.ToString()), prototypeSlot.Production.Operation, (double)procRes));
                                }
                                else
                                {
                                    split = prototypeSlot.Production.Value.Text;
                                    switch (prototypeSlot.Production.Type)
                                    {
                                        case CommonLib.ValueType.Int:
                                        case CommonLib.ValueType.IntVar:
                                        case CommonLib.ValueType.Enum:
                                            {
                                                object changer_val = null;
                                                if (changing.Domain.Contains(split))
                                                    changer_val = split;
                                                else
                                                {
                                                    Slot changer = MLOSource.GetSlotBySplit(split.Substring(split.IndexOf('.') + 1)
                                                        , situationExemplar);
                                                    changer_val = changer.Default;
                                                }
                                                if (prototypeSlot.Production.Type == CommonLib.ValueType.Enum)
                                                {
                                                    if (prototypeSlot.Production.Marker)
                                                    {
                                                        if (prototypeSlot.Production.Operation == "=")
                                                            execute = execute && changing.Default.ToString() == changer_val.ToString();
                                                        else
                                                            execute = execute && changing.Default.ToString() != changer_val.ToString();
                                                    }
                                                    else
                                                        changing.Default = changer_val;
                                                }
                                                else
                                                {
                                                    if (prototypeSlot.Production.Marker)
                                                        execute = execute && MLOSource.CompareOperation(double.Parse(changing.Default.ToString()), prototypeSlot.Production.Operation, double.Parse(changer_val.ToString()));
                                                    else
                                                        changing.Default = changing.Domain.GetValueInRange(MLOSource.DigitOperation(double.Parse(changing.Default.ToString()), prototypeSlot.Production.Operation, double.Parse(changer_val.ToString())));
                                                }
                                            }
                                            break;
                                        case CommonLib.ValueType.Frame:
                                            {
                                                Slot changer = MLOSource.GetSlotBySplit(split.Substring(split.IndexOf('.') + 1)
                                                    , situationExemplar);
                                                if (prototypeSlot.Production.Marker)
                                                {
                                                    if (prototypeSlot.Production.Operation == "=")
                                                        execute = execute && changing.Subframe == changer.Subframe;
                                                    else
                                                        execute = execute && changing.Subframe != changer.Subframe;
                                                }
                                                else
                                                    changing.Subframe = changer.Subframe;
                                                break;
                                            }
                                    }
                                }

                            }
                            if (execute)
                                result.Add(situationExemplar);
                            else
                                bindedSubFrameSets.RemoveAt(i);
                        }
                        break;
                    }
            }
            return result;
        }

        /// <summary>
        /// Проверяет соответствие слота фрейма-экземпляра слоту фрейма-прототипа.
        /// </summary>
        public static bool SlotEquals(Slot prototypeSlot, Slot exemplarSlot)
        {
            if (!(prototypeSlot != null && exemplarSlot != null &&
                 prototypeSlot.Name == exemplarSlot.Name && prototypeSlot.Type == exemplarSlot.Type)) return false;

            switch (prototypeSlot.Type)
            {
                case SlotType.SubFrame:
                    {
                        Frame ParentSubFrame = exemplarSlot.Subframe;
                        return (!ExemplarFrames.ContainsKey(exemplarSlot.Subframe.ID) && prototypeSlot.CanBeNull)
                            || (ParentSubFrame != null && BindSubFrames(NotExemplarFrames[prototypeSlot.Subframe.ID]).Contains(ParentSubFrame));
                    }
                case SlotType.Regular:
                    switch (prototypeSlot.Inherit)
                    {
                        case TypeInherit.Same:
                            return exemplarSlot.Default.ToString() == prototypeSlot.Default.ToString();
                        case TypeInherit.Overwrite:
                            return prototypeSlot.Domain.Contains(exemplarSlot.Default.ToString());
                        case TypeInherit.Range:
                            return prototypeSlot.Domain.Contains(exemplarSlot.Default.ToString(), prototypeSlot.Default.ToString());
                    }
                    break;
            }
            return false;
        }

        public struct FrameSlot
        {
            public FrameSlot(Frame frame, Slot slot)
            {
                Frame = frame;
                Slot = slot;
            }
            public Frame Frame;
            public Slot Slot;
        }

        /// <summary>
        /// Возвращает список наборов привязавшихся к ситуации субфреймов.
        /// </summary>
        public static List<List<FrameSlot>> GetBindSubframesList(List<Slot> slots, int depth = 0)
        {
            List<List<FrameSlot>> result = new List<List<FrameSlot>>();
            if (slots == null || slots.Count == 0) return result;

            Slot slt = slots[depth];
            if (depth < slots.Count - 1)
            {
                List<List<FrameSlot>> query = GetBindSubframesList(slots, depth + 1);
                foreach (Frame fslt in MLOSource.BindSubFrames(NotExemplarFrames[slt.Subframe.ID]))
                {
                    foreach (List<FrameSlot> lfs in query)
                    {
                        List<FrameSlot> added = new List<FrameSlot>();
                        foreach (FrameSlot fs in lfs)
                            added.Add(fs);
                        added.Add(new FrameSlot(fslt, slt));
                        result.Add(added);
                    }
                }
            }
            else
            {
                foreach (Frame fslt in MLOSource.BindSubFrames(NotExemplarFrames[slt.Subframe.ID]))
                {
                    List<FrameSlot> added = new List<FrameSlot>();
                    added.Add(new FrameSlot(fslt, slt));
                    result.Add(added);
                }
            }
            return result;
        }
    }
}
